#include "stdio.h"
#include "stdint.h"
#include "string.h"
#include <stdlib.h>
#include "modbus_crc.h"

#include "main.h"
#include "usart.h"

/**
 * 关于CRC16的学习笔记:
 * 首先要根据协议确定多项式
 * 根据modbus协议CRC16的多项式为 X^16 + X^15 + X^2 + 1
 * 转换为二进制为 0b1000 0000 0000 0101 十六进制为 0x8005
 * 根据不同的通信协议"数据链路层"要求 可能需要进行反转
 * 所以得到 0b1010 0000 0000 0001
 * 最后将其转换为十六进制为 0xA001
 *
 * 校验过程:
 * CRC计算方法
 * 1.预置1个16位的寄存器为十六进制FFFF(即全为1);称此寄存器为CRC寄存器;
 * 2.把第一个8位二进制数据(既通讯信息帧的第一个字节)与16位的CRC寄存器的低8位相异或，把结果放于CRC寄存器;
 * 3.把CRC寄存器的内容右移一位(朝低位)用0填补最高位，并检查右移后的移出位;
 * 4.如果移出位为0:重复第3步(再次右移一位);如果移出位为1:CRC寄存器与多项式A001(1010 0000 0000 0001)进行异或;
 * 5.重复步骤3和4，直到右移8次，这样整个8位数据全部进行了处理;
 * 6.重复步骤2到步骤5，进行通讯信息帧下一个字节的处理;
 * 7.将该通讯信息帧所有字节按上述步骤计算完成后，得到的16位CRC寄存器的高、低字节进行交换;
 *
 * modbus RTU的协议帧构成
 * 字节 |  1   |   2   |  3~n   |    n+2       |    n+3      |
 * 内容 | 地址 | 功能码 | 数据域 | 校验码(低八位) | 校验码(高八位)|
 *
 *
 * 地址码范围为 0-247, 0为广播地址,其中1-247为从机地址 ,248-255是保留地址
 * 主机没有地址, 主机发送从机地址后 从机对主机回应数据 回应的数据也是从机地址 表明是从机进行了回应
 *
 * 功能码
 * 01 读线圈状态 - 读取离散输出的一个位的状态
 * 02 读输入状态 - 读取离散输入的一个位的状态
 * 03 读取保持寄存器 - 读取16位保持寄存器的值 比如说 温度 压力这种hold的数据
 * 04 读取输入寄存器 - 读取16位输入寄存器的值 比如说 电压 电流等模拟量 这种实时变化的
 * 05 强置单线圈 - 强制设置某个离散输出的 位 的状态
 * 06 预置单寄存器 - 预置1个16位寄存器的值
 * 07 读取异常状态
 *
 * 15 强置多个线圈 - 强制设置多个离散输出位的状态
 * 16 预置多个寄存器 - 写入多个16位寄存器的数值
 * 17 报告从机标识
 *
 * 22-64 设备保留
 * 65-72 用户自定义
 *
 * 广播操作时  从机不响应回复
 * 纸飞机上位机使用的功能码为 0x03 0x04方式的数据
 */

#define		CRC_POLY_16			0xA001 //CRC16 多项式
#define		CRC_START_MODBUS	0xFFFF //Modbus CRC初始值

static unsigned char crc_tab16_init = 0; //初始化完成标志
static unsigned short int crc_tab16[256]; // CRC16 表

/**
 * @name 初始化CRC校验码表
 *
 * @brief For optimal performance uses the CRC16 routine a lookup table with values
 * that can be used directly in the XOR arithmetic in the algorithm. This
 * lookup table is calculated by the init_crc16_tab() routine, the first time
 * the CRC function is called.
 *
 * @brief 为了获得最佳性能，使用CRC16例程，查找表中的值可以直接用于算法中的XOR运算。这个查找表是由init_crc16_tab()例程在第一次调用CRC函数时计算的。
 */
void init_crc16_tab(void)
{
  unsigned short int i;
  unsigned short int j;
  unsigned short int crc;
  unsigned short int c;

  for (i = 0; i < 256; i++)
  {
    crc = 0;
    c = i;

    for (j = 0; j < 8; j++)
    {
      if ((crc ^ c) & 0x0001) crc = (crc >> 1) ^ CRC_POLY_16;
      else crc = crc >> 1;

      c = c >> 1;
    }

    crc_tab16[i] = crc;
  }

  crc_tab16_init = 1;
} /* init_crc16_tab */


/**
 * @name unsigned short int crc_modbus( const unsigned char *input_str, unsigned int num_bytes );
 *
 * @brief  The function crc_modbus() calculates the 16 bits Modbus CRC in one pass for
 * a byte string of which the beginning has been passed to the function. The
 * number of bytes to check is also a parameter.
 * @brief crc_modbus()函数 计算校验字节串的前面部分的16位Modbus CRC，并返回。
 */
unsigned short int crc_modbus(const unsigned char *input_str, unsigned int num_bytes)
{
  unsigned short int crc;
  const unsigned char *ptr;
  unsigned int a;

  if (!crc_tab16_init)
  {
    init_crc16_tab();
  }

  crc = CRC_START_MODBUS;
  ptr = input_str;

  if (ptr != 0)
    for (a = 0; a < num_bytes; a++)
    {
      crc = (crc >> 8) ^ crc_tab16[(crc ^ (unsigned short int) *ptr++) & 0x00FF];
    }

  return crc;
} /* crc_modbus */


/* 构造符合ModbusRTU格式的数据。
  【注意】要将纸飞机中Modbus数据类型改为INT16，Modbus字节序改为LITTLE！
   param1[in]: 地址码
   param2[in]: 功能码
   param3[in]: 输入数据首地址
   param4[in]: 输入数据长度
   param5[out]: 输出数据首地址
   param5[out]: 输出数据长度
 */
int convert_to_rtu_data(unsigned char addr_code, unsigned char func_code,
                        unsigned char *in_buff, unsigned char in_size,
                        unsigned char *out_buff, unsigned char *out_size)
{
  uint16_t crc = 0;

  if (!in_buff)
    return -1;
  if (!out_buff)
    return -1;
  if (!out_size)
    return -1;

  out_buff[0] = addr_code;
  out_buff[1] = func_code;
  out_buff[2] = in_size;
  memcpy(&out_buff[3], in_buff, in_size);
  crc = crc_modbus(out_buff, in_size + 3);
  out_buff[in_size + 3] = (crc & 0x00FF) >> 0; //取低字节
  out_buff[in_size + 4] = (crc & 0xFF00) >> 8; //取高字节
  *out_size = in_size + 5;

  return 0;
}

/**
 *
 * @param buff 发送缓冲区地址
 * @param len 缓冲区长度
 */
void send_buff(unsigned char *buff, unsigned char len)
{
  HAL_UART_Transmit(&huart2, buff, len, 1000);
}

void modbus_rtu_send(modbus_data_t *data)
{
  // 输出缓存用于存放转换结果
  uint8_t buff_size = data->param_len + 5;
  uint8_t *buff = (uint8_t*) malloc(buff_size); //分配缓存
  uint8_t buff_len = 0;

  if (convert_to_rtu_data(data->addr, data->func, data->param, data->param_len, buff, &buff_len))
  {
    // printf("convert data failed!\n");
  }
  // 将缓存buff发送出去（给纸飞机），请根据实际更换为你的发送函数
  send_buff(buff, buff_len);

  free(buff);//释放缓存
}
